#include "xmlpositionparser.h"

XmlPositionParser::XmlPositionParser()
{
    doc = new QDomDocument("PositionXML");
    QFile file(":/positions.xml");
    if (!file.open(QIODevice::ReadOnly))
        return;
    if (!doc->setContent(&file)) {
        file.close();
        return;
    }
    file.close();

    rootElem = doc->documentElement();
}

QList< QStringList > XmlPositionParser::getTree(QString cls, QString ins, QString typ, QString ton)
{
    //qWarning() << "Method deprecated, fall back to getTree(QStringList) instead";

    QStringList string_list;
    string_list << cls << ins << typ << ton;
    return getTree(string_list);
}

QList< QStringList > XmlPositionParser::getTree(QStringList string_list)
{
    // Will contain the partial tree
    QList<QStringList> string_tree;

    // Specify to which depth the instrument is specified
    int depth = string_list.length();

    // Contain the depth chain to verify element is correct
    QStringList depth_chain;
    depth_chain << "class" << "instrument" << "type" << "tonality";

    // Starting with the rist root node's child
    QDomElement current = rootElem.firstChildElement(depth_chain.at(0));

    for(int d = 0; d < 4; d++) {
        // Will contain list of all elements at height d
        QStringList list;
        // Will contain a child to move on next cycle
        QDomElement  next;
        QDomElement  first = current;
        // Iterate along the same depth d
        while( !current.isNull() ) {
            // If the element is somewhat invalid (and you do not have to skip his childs, prit a warning
            if(  !current.hasAttribute("name") ) {
                qWarning() << "Parsing wanring:" << "" << "has not attribute 'name', which is mandatory, fall back to first element.";
            // If you found a matching child and you are not at the last node
            } else if( d != 3 && current.attribute("name") == string_list.at(d) ) {
                next = current.firstChildElement(depth_chain.at(d + 1));
            }
            // Add current node name to the tree composition
            list << current.attribute("name");

            // Go to next element
            current = current.nextSiblingElement();
        }
        // Verify if you have to go to the first child, if you are not at the last note
        if ( d != 3 && ( depth <= d || next.isNull() ) ) {
            next = first.firstChildElement(depth_chain.at(d + 1));
        }
        // Add list to tree
        string_tree << list;
        //qDebug() << "List at depth" << d << ":" << list << "starting from:" << string_list;

        // Go to child node if there is no child node break
        if( next.isNull() ) break;
        current = next;
    }


    // Add missing strings (to uniform depth of the tree to 4)
    QStringList simple_list;
    simple_list << "N/D";
    while( string_tree.length() < 4) {
        string_tree << simple_list;
    }

    // Return complete tree
    return string_tree;
}

QDomElement XmlPositionParser::goToElement(QString cls, QString ins, QString typ, QString ton)
{
    //qWarning() << "Method deprecated, fall back to goToElement(QStringList) instead";

    QStringList string_list;
    string_list << cls << ins << typ << ton;
    return goToElement(string_list);
}

QDomElement XmlPositionParser::goToElement(QStringList string_list)
{
    // Go to first element
    QDomElement node = rootElem;

    // Contain the depth chain to verify element is correct
    QStringList depth_chain;
    depth_chain << "class" << "instrument" << "type" << "tonality";

    for(int d = 0; d < 4; d++) {
        QDomElement child;
        // Search for a node matching the depth, if you don't found it just break
        child = node.firstChildElement(depth_chain.at(d));
        if ( child.isNull() ) break;
        node = child;
        // Else if you found a matching child search for the correct name
        while( !node.isNull() ) {
            if(node.attribute("name") == string_list.at(d)) break;
            node = node.nextSiblingElement();
        }
    }

    // Return matching node
    return node;
}

QList < BasePosition > XmlPositionParser::getBasePositions(QDomElement e)
{
    QList < BasePosition > base_pos_list;

    // Just go to first "note element", e points always to current instrument
    QDomElement n = e.firstChildElement();

    // Pass through all notes from the first to the last
    while(!n.isNull()) {
        QString name = n.nodeName();
        // Handle notes
        if (name == "position") {
            base_pos_list.append(BasePosition(n.attribute("intname"), n.attribute("name") ,n.attribute("img")));
        // Handle import
        } else if (name == "import") {
            if( n.hasAttribute("class") && n.hasAttribute("instrument") && n.hasAttribute("type") && n.hasAttribute("tonality")) {
                QDomElement e_imported = goToElement(n.attribute("class"), n.attribute("instrument"), n.attribute("type"), n.attribute("tonality"));
                base_pos_list.append(getBasePositions(e_imported));
            }
        // Handle ranges
        } else if (name == "range") {
            QDomElement from = n.firstChildElement("from");
            QDomElement to = n.firstChildElement("to");

            Note from_note = Note(from.attribute("pitch"), from.attribute("alt"), from.attribute("oct"));
            Note to_note = Note(to.attribute("pitch"), to.attribute("alt"), to.attribute("oct"));

            //qDebug() << "Found range from" << from_note.toQString() << "to" << to_note.toQString();

            int from_int = from_note.getCentsOffset();
            int to_int = to_note.getCentsOffset();

            for(int i = from_int; i <= to_int; i+=100) {
                Note note = Note(i, Note());
                QString name = note.toQString();
                QString string = "Position %1";
                BasePosition basepos = BasePosition(string.arg(i), name);
                base_pos_list.append(basepos);
                //qDebug() << "Appending note:" << note.toQString();
            }
        }
        n = n.nextSiblingElement();
    }

    return base_pos_list;
}

BasePosition XmlPositionParser::getBasePosition(QString name, QDomElement e)
{
    QList < BasePosition > base_pos_list = getBasePositions(e);

    for(int i = 0; i < base_pos_list.length(); i++) {
        BasePosition basepos = base_pos_list.at(i);
        if(basepos.getInternName() == name) return basepos;
    }
    return BasePosition();
}

QList < Position > XmlPositionParser::getPositions(QDomElement e)
{
    // Will contain all positions
    QList < Position > pos_list;

    // Just go to fist "note element", e points always to current instrument
    QDomElement n = e.firstChildElement();

    // Pass through all notes from the first to the last
    while(!n.isNull()) {
        QString name = n.nodeName();
        // Handle notes
        if (name == "note") {
            // Create a new note
            Note note = Note(n.attribute("pitch"), n.attribute("alt"), n.attribute("oct"));

            QString pos_string = n.attribute("positions");
            QStringList pos_string_list = pos_string.split(",", QString::SkipEmptyParts);

            // Iterate through all position of the note
            for(int index = 0; index < pos_string_list.length(); index++) {

                // Extract position information
                double posfreq;
                // Frequency for the note
                posfreq = note.getFreq();
                if ( n.hasAttribute("freq") ) {
                    posfreq = n.attribute("freq").split(",").at(index).toDouble();
                }
                QString msg = "";
                if ( n.hasAttribute("msg") ) {
                    //qDebug() << "Found a message" << n.attribute("msg");
                    msg = n.attribute("msg").split(",").at(index);
                    //qDebug() << msg;
                }

                // Create a new base position
                BasePosition basepos = getBasePosition(pos_string_list.at(index), e);

                // Create a new position
                Position pos = Position(basepos, note, posfreq, msg);

                // Add position and go to next
                pos_list.append(pos);
            }
        // Handle import
        } else if (name == "import") {
            if( n.hasAttribute("class") && n.hasAttribute("instrument") && n.hasAttribute("type") && n.hasAttribute("tonality")) {
                QDomElement e_imported = goToElement(n.attribute("class"), n.attribute("instrument"), n.attribute("type"), n.attribute("tonality"));
                pos_list.append(getPositions(e_imported));
            }
        } else if (name == "range") {
            QDomElement from = n.firstChildElement("from");
            QDomElement to = n.firstChildElement("to");

            Note from_note = Note(from.attribute("pitch"), from.attribute("alt"), from.attribute("oct"));
            Note to_note = Note(to.attribute("pitch"), to.attribute("alt"), to.attribute("oct"));

            //qDebug() << "Found range from" << from_note.toQString() << "to" << to_note.toQString();

            int from_int = from_note.getCentsOffset();
            int to_int = to_note.getCentsOffset();

            for(int i = from_int; i <= to_int; i+=100) {
                Note note = Note(i, Note());
                QString name = note.toQString();
                QString string = "Position %1";
                BasePosition basepos = BasePosition(string.arg(i), name);
                pos_list.append(Position(basepos, note, note.getFreq()));
                //qDebug() << "Appending note:" << note.toQString();
            }
        }
        n = n.nextSiblingElement();
}

    return pos_list;
}

Instrument XmlPositionParser::getInstrument(QString elem1, QString elem2, QString elem3, QString elem4)
{
    // Base initialization
    Instrument ins = Instrument();
    ins.setClassName(elem1);
    ins.setInstrumentName(elem2);
    ins.setTypeName(elem3);
    ins.setTonaltyName(elem4);

    // Goto correct instrument
    QDomElement base_elem = goToElement(elem1, elem2, elem3, elem4);

    // Update otional attributes
    if(base_elem.hasAttribute("baseclef")) ins.setClef(Clef(base_elem.attribute("baseclef")));
    if(base_elem.hasAttribute("baseoct")) ins.setOctave(Octave(base_elem.attribute("baseoct")));
    if(base_elem.hasAttribute("tonality")) ins.setTonality(EquiNote(base_elem.attribute("tonality")));
    if(base_elem.hasAttribute("transpose")) ins.setTranspose(EquiNote(base_elem.attribute("transpose")));
    else ins.setTranspose();
    if(base_elem.hasAttribute("transposeoct")) ins.setTransposeOctave(Octave(base_elem.attribute("transposeoct")));
    else ins.setTransposeOctave();

    // Will contain every position of the instrument
    QList < Position > pos_list;
    QList < BasePosition > base_pos_list;

    // Get positions and set them
    pos_list = getPositions(base_elem);
    ins.setPositions(pos_list);

    // Get base positions and set them
    base_pos_list = getBasePositions(base_elem);
    ins.setBasePositions(base_pos_list);

    return ins;
}
